Threshold mode allows multiple devices to approve the same Pulse.
PPS version 1 uses n-of-m Ed25519 multisignature.
A threshold group contains:
gid
threshold
signers
last_counter
revoked
Example:
use Pps\Crypto\Base64Url;
use Pps\Key\KeyManager;
$gid = KeyManager::generateGid();
$gidB64u = Base64Url::encode($gid);
$phoneKey = KeyManager::generateKeyPair();
$watchKey = KeyManager::generateKeyPair();
$phoneKid = Base64Url::encode(KeyManager::generateKid());
$watchKid = Base64Url::encode(KeyManager::generateKid());
$storage->saveGroupState($gidB64u, [
'gid' => $gidB64u,
'threshold' => 2,
'signers' => [
$phoneKid => $phoneKey->publicKeyB64u(),
$watchKid => $watchKey->publicKeyB64u(),
],
'last_counter' => 0,
'revoked' => false,
]);
1. Device A creates a partial Pulse payload.
2. Device A shares the payload with Device B.
3. Device B verifies the transaction details.
4. Device B signs the same payload.
5. Signatures are collected.
6. The final Pulse Token is assembled.
7. The server verifies the required number of signatures.
use Pps\Client\AuthenticatorClient;
$phoneClient = new AuthenticatorClient($phoneState);
$partial = $phoneClient
->rpId('example.com')
->nonce($nonceB64u)
->amountMinor(5000000)
->thresholdGroup($gidB64u, 2)
->createPartialPulse();
$watchClient = new AuthenticatorClient($watchState);
$coSign = $watchClient->coSign($partial);
$token = AuthenticatorClient::assembleThresholdToken($partial, [
$phoneKid => $phoneSignatureB64u,
$watchKid => $coSign['signature'],
]);
The server verifies:
1. Group exists and is not revoked.
2. Threshold value matches stored threshold.
3. Each signer is registered.
4. Each signature is valid.
5. Number of valid signatures >= threshold.
6. Group counter increases.