> For the complete documentation index, see [llms.txt](https://bsv.brc.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bsv.brc.dev/wallet/0229.md).

# Wallet-Native Elliptic Curve Point Multiplication as a BRC-98 Module

Connor Murray (<connor.murray@bsvassociation.org>)

## Abstract

This proposal defines `ecpm`, a [BRC-98](/wallet/0098.md) permission-module scheme for applying a wallet-derived secp256k1 scalar to an arbitrary caller-supplied curve point, or removing that scalar by applying its modular inverse.

The operation is carried over the existing [BRC-100](/wallet/0100.md) `getPublicKey` method. Inside the reserved `p ecpm` namespace, a supporting wallet interprets the method semantically as elliptic-curve point multiplication and returns the normal `{ publicKey }` result. No method, call code, Wallet Wire message, or optional member is added to BRC-100.

The scheme enables commutative-masking protocols such as mental poker and verifiable shuffles while keeping the derived scalar inside the wallet. Wallets that do not install the module retain BRC-98's required behavior and reject the reserved protocol.

## Motivation

A class of multi-party protocols depends on the commutativity of scalar multiplication:

```
a·(b·P) = b·(a·P)
```

Barnett-Smart mental poker is a representative use. Each participant applies a secret scalar to encoded card points, and later removes that scalar in any order. The application needs `d·P` and `d⁻¹·P` for an arbitrary point `P`, but it must not learn `d`.

Ordinary BRC-100 `getPublicKey` does not supply this operation. It performs BRC-42/43 child-key derivation and returns a derived public key. Naming `P` as the BRC-43 counterparty influences the child derivation, but does not replace the generator with `P`. Depending on `forSelf`, the result is the wallet's or counterparty's derived child public key, not the wallet's derived private scalar multiplied by the caller's point.

This distinction matters when `P` is an intermediate masked point whose discrete logarithm is intentionally unknown. From `d·G` and `P`, an application cannot compute `d·P` without solving a discrete logarithm or already knowing the scalar of `P`.

Existing key-linkage and encryption methods can be composed to emulate some forward-multiplication cases, but that is not an equivalent contract. It couples a point operation to linkage disclosure and encryption semantics, does not provide multiplication by `d⁻¹`, and grants permissions for capabilities the application did not mean to request.

The missing behavior therefore cannot be obtained from pure BRC-43 naming alone. It requires a wallet-side semantic operation, but it does not require changing BRC-100.

## Relationship to BRC-98

BRC-98 reserves protocol identifiers beginning with `p` , requires unsupported wallets to reject them, and permits a supported scheme to define its own rules for permitted operations, key IDs, counterparties, permission attributes, and execution.

The `ecpm` scheme uses that reserved dispatch point to specialize the meaning of an existing BRC-100 method. This proposal does not amend BRC-98. A clarification in discussion of BRC-98 may record that semantic specialization of an existing method inside a supported module namespace is an intended use of the module system; a separate amendment to BRC-98 is not required.

The semantic boundary is safe for compatibility:

1. Outside `p ecpm`, `getPublicKey` retains its ordinary BRC-100 meaning.
2. Inside `p ecpm`, a wallet either implements this scheme or rejects the request as BRC-98 already requires.
3. Applications never infer support from a new optional wallet member. They use a configured supporting wallet or handle the standard unsupported-scheme error.

## Specification

### Scheme and method

The BRC-98 scheme ID is:

```
ecpm
```

A conforming implementation MUST accept this scheme only through the existing BRC-100 `getPublicKey` method. It MUST reject every other BRC-100 method requested under `p ecpm`. This prevents the ECPM scalar from being reused for signing, HMAC, encryption, or another cryptographic purpose.

The protocol-name component of `GetPublicKeyArgs.protocolID` has this grammar:

```
p ecpm <operation> <pointHex> <logicalProtocolID>
```

where:

* `operation` is exactly `apply` or `remove`;
* `pointHex` is a lowercase, 66-character compressed secp256k1 public-key encoding;
* `logicalProtocolID` is the application protocol whose ECPM key universe is being requested.

The outer BRC-100 call remains:

```ts
wallet.getPublicKey(
  {
    protocolID: [
      securityLevel,
      `p ecpm ${operation} ${pointHex} ${logicalProtocolID}`
    ],
    keyID,
    counterparty,
    privileged,
    privilegedReason,
    seekPermission
  },
  originator
)
```

`keyID`, `counterparty`, `privileged`, `privilegedReason`, `seekPermission`, and `originator` retain their existing BRC-100 meanings and locations. They MUST NOT be duplicated or encoded into the protocol string.

`counterparty` defaults to `self` under the existing method rules. `identityKey: true` is prohibited. `forSelf` MUST be absent or false; it does not alter ECPM semantics.

### Logical protocol ID

The logical protocol ID MUST:

* contain only lowercase ASCII letters, numbers, and single spaces;
* contain no leading, trailing, or repeated spaces;
* be at least 5 characters and no more than 273 characters; and
* not end with `protocol`.

The 273-character ceiling ensures that the canonical derivation protocol `p ecpm <logicalProtocolID>` remains within BRC-43's 280-character protocol-ID limit.

The complete outer `p ecpm` string is a BRC-98 dispatch envelope, not the protocol ID used for BRC-43 derivation. It MAY therefore exceed BRC-43's 280-character limit so that the point and operation metadata do not reduce the logical protocol namespace. It MUST be no more than 354 characters: an `apply` request can be at most 353 characters and a `remove` request can be at most 354 characters. All permitted characters are ASCII, so these character and byte counts are identical. Both bounds remain within BRC-100's 400-character protocol-string limit.

A key ID MUST satisfy the active BRC-100 `getPublicKey` key-ID limit.

### Canonical scalar derivation

Let:

* `s` be the security level in the outer `protocolID` tuple;
* `L` be `logicalProtocolID`;
* `K` be the separately supplied `keyID`; and
* `C` be the separately supplied `counterparty`, defaulting to `self`.

The module MUST derive a nonzero private scalar `d` using BRC-42/43 with:

```
protocolID  = [s, "p ecpm " + L]
keyID      = K
counterparty = C
```

The point and operation MUST NOT form part of this derivation identity.

This omission is load-bearing. If the point were part of the invoice, every point would select a different scalar. If the operation were part of the invoice, `remove` would select a different scalar from `apply`. In either case round-trip removal and commutativity would fail.

The `p ecpm` prefix in the canonical derivation protocol isolates ECPM scalars from ordinary application keys using the same logical protocol and key ID.

### Operation

After validating the point as `P`:

* `apply` returns `d·P`;
* `remove` returns `d⁻¹·P`, where the inverse is computed modulo the secp256k1 group order `n`.

The successful result MUST use the ordinary `getPublicKey` result shape:

```ts
{ publicKey: PubKeyHex }
```

The returned value MUST be the lowercase compressed encoding of the resulting point.

For identical security level, logical protocol ID, key ID, counterparty, and root-key selection:

```
remove(apply(P)) = P
```

Implementations MUST reject, rather than encode, a result at infinity.

### Privileged keys

The existing `privileged` and `privilegedReason` fields select privileged-key behavior.

When `privileged` is absent or false, the module derives `d` from the wallet's ordinary BRC-42/43 root.

When `privileged` is true:

1. `privilegedReason` MUST be present and satisfy the existing BRC-100 description constraint.
2. The wallet MUST obtain explicit authorization even at security level 0.
3. The wallet MUST derive `d` from its privileged key material using the same canonical ECPM derivation tuple.
4. The privileged root or key-derivation capability MUST NOT be exposed to the application.
5. The wallet MUST fail closed if privileged derivation is unavailable.

A wallet implementation SHOULD request access to privileged material only after the operation and reason have been authorized, and SHOULD release that access according to its existing privileged-key policy.

### Permissions

The module applies BRC-43 permission semantics to the logical ECPM protocol:

* an ordinary security-level-0 request does not require a prompt;
* a security-level-1 grant is scoped to the originator, logical protocol ID, and ordinary-versus-privileged root selection;
* a security-level-2 grant additionally distinguishes the counterparty; and
* ordinary and privileged grants MUST NOT satisfy one another.

A wallet MAY cache a successful grant according to its normal permission lifecycle. The point, operation, and key ID SHOULD be displayed or made available to the authorization UI, but they do not change the BRC-43 protocol-level grant scope.

If permission is required and no applicable grant exists:

* `seekPermission: false` MUST fail without prompting;
* otherwise the wallet MAY seek authorization through its normal trusted UI.

Concurrent equivalent requests SHOULD share one pending authorization decision so that high-volume protocols cannot produce duplicate prompts.

### Point validation

Before multiplication, a conforming implementation MUST reject the input unless all of these conditions hold:

1. The encoding is exactly 33 bytes represented by 66 lowercase hexadecimal characters.
2. The first byte is `02` or `03`.
3. The encoded x-coordinate is less than the secp256k1 field prime `p`.
4. The encoding decodes to a point satisfying `y² = x³ + 7 (mod p)`.
5. The point is finite.

The x-coordinate range check MUST occur before a parser that reduces coordinates modulo `p`. Some curve parsers accept `02` followed by 32 `ff` bytes, reduce the x-coordinate, and then report the resulting different point as valid.

The same canonical validation MUST be applied when `counterparty` is supplied as a public key. The special values `self` and `anyone` remain valid.

### Errors

A conforming wallet MUST fail without performing multiplication when:

* the installed method is not `getPublicKey`;
* the protocol grammar or any field constraint is invalid;
* identity-key or `forSelf: true` behavior is requested;
* the point or public-key counterparty is invalid;
* authorization is unavailable or denied;
* privileged derivation is requested but unavailable; or
* the operation would return infinity.

Errors SHOULD identify the unsupported scheme, invalid field, or denied capability without revealing key material.

### Conformance vectors

The vectors below pin the canonical derivation and the group operation. They were generated with the BRC-42 derivation as implemented in `bsv-blockchain/go-sdk` (`PrivateKey.DeriveChild`), which is cross-tested against the BRC-42 specification vectors.

All vectors share these parameters:

* security level: `2`
* logical protocol ID: `mental poker deal`
* key ID: `deck mask 1`
* canonical derivation protocol: `p ecpm mental poker deal`
* BRC-43 invoice number: `2-p ecpm mental poker deal-deck mask 1`

Test wallets use transparent, minimal root keys, and the base point is the public key of the scalar 5:

```
wallet A root private key:  0000000000000000000000000000000000000000000000000000000000000002
wallet A identity key:      02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
wallet B root private key:  0000000000000000000000000000000000000000000000000000000000000003
wallet B identity key:      02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
base point P:               022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4
```

**Vector 1 — apply, counterparty `self`.** Wallet A receives:

```
protocolID = [2, "p ecpm apply 022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4 mental poker deal"]
keyID = "deck mask 1"
counterparty = "self"
```

With counterparty `self`, the BRC-42 shared secret is computed against the wallet's own identity key. The derived scalar and result are:

```
d_A:     0d7da380de7459582c600748e94539c408742f9d4f21153473ba2c1cb812a984
d_A·P:   023414ffa833cb89ab8cdd631998921d6a7b05b77931d0c50c80740324df6ce4f2
```

**Vector 2 — remove round-trip.** Wallet A receives the vector 1 result with operation `remove` and otherwise identical arguments. The output is `P` exactly:

```
d_A⁻¹·(d_A·P) = 022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4
```

**Vector 3 — multi-party commutativity.** Wallet B, with the same arguments and counterparty `self`, derives:

```
d_B:     d205df1fd5f36ed688c66dda212d1178b87e11cad37469a98c7b3edd690f2b3b
```

Applying both masks to `P` yields the same point in either order:

```
d_B·(d_A·P) = d_A·(d_B·P) = 035eebd9a281022ce46ea790bd460f6c6320b08a9e07b560b4da36480605c0580d
```

**Vector 4 — counterparty separation.** Wallet A with `counterparty` set to wallet B's identity key, all else as in vector 1, derives a different scalar and result:

```
d_A':    c3dd11d61386d969ff40e150e3039eeab9bda24ffa13936eac29d0347ec5104b
d_A'·P:  032406ec05e51aac7d0dcb1c7f1f3143b37e6336440ab728a552d668f2e397b8a4
```

**Vector 5 — rejected encodings.** Each of these `pointHex` values MUST be rejected without multiplication:

| Input                                                                | Reason                                                                                |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` | x-coordinate not less than the field prime; must be caught before any reducing parser |
| `020000000000000000000000000000000000000000000000000000000000000005` | x³ + 7 is not a quadratic residue; no such point exists                               |
| `04…` (65-byte uncompressed encoding)                                | not the 33-byte compressed form                                                       |
| any 64-character value                                               | wrong length                                                                          |
| vector 1's point with any uppercase hex digit                        | not the canonical lowercase encoding                                                  |

Privileged derivation (root-specific) and unsupported-module rejection are behavioral requirements without portable vectors: a wallet MUST produce different scalars for ordinary and privileged requests with otherwise identical arguments, and a wallet without the module MUST reject the reserved protocol as BRC-98 requires.

Protocol-length boundaries are also normative: implementations MUST accept a 273-character logical protocol ID for both operations, including the resulting 354-character `remove` envelope, and MUST reject a 274-character logical protocol ID.

## TypeScript reference module

The TypeScript reference implementation is proposed in [bsv-blockchain/ts-stack#488](https://github.com/bsv-blockchain/ts-stack/pull/488) as the installable `@bsv/ecpm-permission-module` package.

It extends the Wallet Toolbox permission-module interface with an optional semantic handler:

```ts
handleRequest?: (
  request: { method: string; args: object; originator: string },
  next: (args: object) => Promise<unknown>
) => Promise<unknown>
```

A semantic handler may return a conforming BRC-100 result directly, or invoke `next` at most once to use the underlying wallet method. Existing `onRequest` and `onResponse` transformation modules remain source-compatible.

The ECPM module returns the result directly and never forwards its request to ordinary `getPublicKey`; forwarding would derive `d·G` or a BRC-42 child public key, not `d·P`.

A wallet host installs it alongside other BRC-98 modules under the `ecpm` scheme and supplies:

* its ordinary BRC-42/43 key deriver;
* its trusted authorization callback; and
* optionally, a privileged-key-deriver provider.

The key derivers and derived scalars remain inside trusted wallet/module code and are never included in the BRC-100 response.

## Security

### Key isolation

For a counterparty point `Q`, `d·Q` is an ECDH shared secret. Reusing an identity, spending, signing, HMAC, or encryption key for ECPM could therefore disclose a capability belonging to another protocol.

For this reason, the canonical `p ecpm <logicalProtocolID>` derivation namespace and the method restriction are mandatory. A wallet MUST NOT substitute its identity key, spending key, or a key from an ordinary non-ECPM protocol.

### Chosen-point requests

For a valid prime-order secp256k1 point `P`, observing `P` and `d·P` does not reveal `d` without solving the elliptic-curve discrete logarithm problem. An adaptively selected valid point does not weaken this assumption in the prime-order group.

That argument depends on strict validation. Accepting a point on another curve or a non-canonical encoding can create small-subgroup or invalid-curve attacks that leak information about `d`. Implementations must validate the encoded point before multiplication.

### Permission and denial-of-service considerations

Mental-poker and shuffle protocols can require hundreds of operations, so prompting once per point is impractical. Protocol-scoped grants allow the intended throughput. Wallets SHOULD still bound request concurrency and resource use, and SHOULD make the logical protocol, originator, operation, key ID, counterparty, point, and privileged reason available to trusted policy code.

### Capability containment

The public application interface exposes only `getPublicKey` and receives only a compressed public point. The semantic module is trusted wallet code: it may use internal key-derivation capabilities, but it MUST NOT return those capabilities, a private scalar, or privileged root material.

## Why pure BRC-43 is insufficient

BRC-43 can name the counterparty and thereby determine which shared derivation universe is used. It cannot name a replacement generator for the requested group operation.

If the arbitrary point is supplied as `counterparty`, ordinary `getPublicKey` performs BRC-42 child derivation relative to that counterparty. It does not compute the wallet's derived private scalar times that point. If `getPublicKey` returns `d·G`, combining it with an arbitrary `P` still does not yield `d·P` unless the application knows the discrete logarithm of `P`. Intermediate points in a commutative-masking protocol are constructed specifically so that no one knows that logarithm.

Pure BRC-43 therefore provides the scalar namespace and permission vocabulary used by this proposal, but not the required group operation. BRC-98 supplies the missing semantic dispatch without changing BRC-100.

## References

* [BRC-2: Data Encryption and Decryption](/wallet/0002.md)
* [BRC-42: BSV Key Derivation Scheme](/key-derivation/0042.md)
* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043.md)
* [BRC-44: Admin-reserved and Prohibited Key Derivation Protocols](/key-derivation/0044.md)
* [BRC-73: Group Permissions for App Access](/wallet/0073.md)
* [BRC-98: P Protocols](/wallet/0098.md)
* [BRC-100: Unified Wallet-to-Application Interface](/wallet/0100.md)
* [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bsv.brc.dev/wallet/0229.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
