Skip to content
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
5fa4714
test(sdk): open an entry with the first of a wallet's prepared keys
QuantumExplorer Nov 8, 2026
1549627
feat(sdk): send, scan and read private contact requests
QuantumExplorer Nov 8, 2026
ef1a5ce
feat(wasm-sdk): read private document pages and seal and scan private…
QuantumExplorer Nov 8, 2026
a6ca844
feat(sdk): name the sender key of a recovered private contact request
QuantumExplorer Nov 8, 2026
c3f1b0d
feat(sdk): sign private contact requests with the sender's signing key
QuantumExplorer Nov 8, 2026
81e334e
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
4ffcad9
fix(wasm-sdk): sign private contact requests with the identity signer
QuantumExplorer Nov 8, 2026
6db333e
fix(sdk): bind the recipient key into a private request's signature
QuantumExplorer Nov 8, 2026
3d510b8
fix(sdk): export private_contact_request_signing_keys
QuantumExplorer Nov 8, 2026
888068a
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
d4efa2e
fix(sdk): validate private requests with dpp and check proved identit…
QuantumExplorer Nov 8, 2026
f3f6488
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
9d347a5
fix(wasm-sdk): load a page's contract first and wipe read keys on eve…
QuantumExplorer Nov 8, 2026
473a440
fix(sdk): refuse expired signing keys and refresh a DashPay without t…
QuantumExplorer Nov 8, 2026
d15e3b0
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
4a4826a
fix(sdk): keep encryption keys inside their contract bounds and check…
QuantumExplorer Nov 8, 2026
30c8f6a
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
785e493
fix(sdk): give an empty context provider the DashPay it fetches, and …
QuantumExplorer Nov 8, 2026
9ebc1b6
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
b3a0ad3
fix(wasm-dpp2): key the constructor's tokens by position as text, and…
QuantumExplorer Nov 8, 2026
e7b8eac
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
f5dde30
fix(wasm-dpp2): refuse tokens for a contract that cannot hold them
QuantumExplorer Nov 8, 2026
0ce4688
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
410b38c
fix(sdk): refuse a scan of another store as DashPay private contact r…
QuantumExplorer Nov 8, 2026
2daee2b
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 8, 2026
ab68754
refactor(dpp): give the document signing levels of a security require…
QuantumExplorer Nov 8, 2026
1bd3788
fix(sdk): read a private contact request only through a key it could …
QuantumExplorer Nov 8, 2026
609f529
fix(sdk): wipe the outgoing viewing key a private contact request is …
QuantumExplorer Nov 8, 2026
5839100
fix(sdk): give a private page reader's context provider the contract …
QuantumExplorer Nov 8, 2026
be7fba8
feat(sdk): stream a private document store's pages, many fetched at once
QuantumExplorer Nov 8, 2026
b8e41ab
fix(sdk): refuse a private document read below protocol version 15 as…
QuantumExplorer Nov 8, 2026
d91c1c6
fix(sdk): name the SDK's protocol version when building a private doc…
QuantumExplorer Nov 8, 2026
0e3f521
fix(wasm-dpp2): refuse tokens set on a data contract that holds none
QuantumExplorer Nov 8, 2026
3f546c0
docs(dpp): say at the batch signing levels why the in-place change ke…
QuantumExplorer Nov 8, 2026
e710dd7
docs(sdk): say what the private contact request parser and signing le…
QuantumExplorer Nov 8, 2026
454f25c
test(sdk): check a recipient key holding another point is refused by …
QuantumExplorer Nov 8, 2026
3b68903
test(sdk): pin a private page stream's order and its cursor under a g…
QuantumExplorer Nov 8, 2026
898e8fc
fix(sdk): keep the store a private document scan was read from, and r…
QuantumExplorer Nov 8, 2026
91d8782
fix(sdk): send a private document transition only once the network is…
QuantumExplorer Nov 9, 2026
19ab417
fix(sdk): keep each outgoing viewing key in place as scan senders are…
QuantumExplorer Nov 9, 2026
fd3134b
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 9, 2026
3571bb8
fix(wasm-sdk): read private document pages against the contract that …
QuantumExplorer Nov 9, 2026
b8780f5
test(wasm-sdk): test the private contact request bindings' conversion…
QuantumExplorer Nov 9, 2026
59e047a
fix(sdk): learn a private store's chunk size only from a page that en…
QuantumExplorer Nov 9, 2026
314d957
fix(sdk): refuse a private contact request scan of a contract whose t…
QuantumExplorer Nov 9, 2026
4d30b82
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 9, 2026
e11ef7c
fix(sdk): keep a newer contract over a registered older one
QuantumExplorer Nov 9, 2026
04a7f96
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 9, 2026
efa0fc8
fix(sdk): complete a private store chunk an older snapshot ended part…
QuantumExplorer Nov 9, 2026
baa3092
fix(sdk): gate every type 29 broadcast and refuse it with the structu…
QuantumExplorer Nov 9, 2026
40e4f57
fix(sdk): keep a newer contract in the gRPC context provider, and say…
QuantumExplorer Nov 9, 2026
f7fcc44
fix(sdk): report an identity proved before its entry as a retryable s…
QuantumExplorer Nov 9, 2026
80094d0
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 9, 2026
b065ca3
fix(sdk): read a private contact request under an eligible alias of i…
QuantumExplorer Nov 9, 2026
4712db7
fix(sdk): fetch again a private page an older snapshot left empty, th…
QuantumExplorer Nov 9, 2026
8558e83
feat(sdk): schedule a private store's page walk from the chunk size i…
QuantumExplorer Nov 9, 2026
741c972
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 9, 2026
75279c6
test(wasm-sdk): build test pages with the chunk power the SDK page no…
QuantumExplorer Nov 9, 2026
e5112af
perf(wasm-sdk): store a private type's contract once a version, not o…
QuantumExplorer Nov 9, 2026
4386ec0
docs(sdk): say that a private request's key expiry is the identity's …
QuantumExplorer Nov 9, 2026
699693f
Merge remote-tracking branch 'origin/feat/sdk-private-documents-send-…
QuantumExplorer Nov 9, 2026
f9d669f
fix(sdk-trusted-context-provider): keep a newer known contract over a…
QuantumExplorer Nov 9, 2026
311ae62
fix(sdk): keep a failed refresh as the cause of a private document se…
QuantumExplorer Nov 9, 2026
4e75e27
fix(sdk): read a private page against a newer contract with the type …
QuantumExplorer Nov 9, 2026
235a4f8
fix(sdk): take the network as proven below a version only on a curren…
QuantumExplorer Nov 9, 2026
956e962
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 9, 2026
ecf621d
docs(wasm-sdk): say that a registration keeps a held contract of a hi…
QuantumExplorer Nov 9, 2026
0dc274c
fix(sdk): ask the protocol gate before refusing a private document send
QuantumExplorer Nov 9, 2026
9b8589a
test(drive-abci): export a protocol 15 epoch probe for the SDK's gate…
QuantumExplorer Nov 9, 2026
50658b5
fix(sdk): establish protocol 15 through the gate for DashPay's privat…
QuantumExplorer Nov 9, 2026
6a9fea5
Merge branch 'feat/sdk-private-documents-send-and-scan' into feat/was…
QuantumExplorer Nov 9, 2026
84c2b96
feat(platform-version): double a private document store's chunk targe…
QuantumExplorer Nov 9, 2026
52cdd2a
Merge remote-tracking branch 'origin/feat/sdk-private-documents-send-…
QuantumExplorer Nov 9, 2026
e083164
Merge remote-tracking branch 'origin/v5.1-dev' into feat/wasm-sdk-pri…
QuantumExplorer Nov 9, 2026
c5d08bb
test(drive-abci): expect DashPay v3's private store at chunk power 10…
QuantumExplorer Nov 9, 2026
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
61 changes: 61 additions & 0 deletions packages/js-evo-sdk/src/private-documents/facade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as wasm from '../wasm.js';
import type { EvoSDK } from '../sdk.js';

/**
* Private document types (protocol version 15) and DashPay's private contact requests.
*
* A private type's documents are fixed-size encrypted entries of a store, in append order,
* readable a page at a time. A DashPay private contact request is one such entry sealed to the
* recipient's DashPay key: only the recipient learns it exists, who sent it and what it holds.
*
* Appending an entry is paid from the shielded pool, which needs an Orchard proof this SDK
* cannot make yet (see `ShieldedFacade`): `sealContactRequest` builds the entry, and a wallet
* that can prove appends it.
*/
export class PrivateDocumentsFacade {
private sdk: EvoSDK;

constructor(sdk: EvoSDK) {
this.sdk = sdk;
}

/**
* The proved page of a private type's store from `startPosition`, up to the end of the
* store's chunk holding it. A reader walks the store by asking again from the position after
* the page's last entry, until `totalCount`.
*/
async entries(
dataContractId: wasm.IdentifierLike,
documentTypeName: string,
startPosition: bigint,
): Promise<wasm.PrivateDocumentEntriesPage> {
const w = await this.sdk.getWasmSdkConnected();
return w.getPrivateDocumentEntries(dataContractId, documentTypeName, startPosition);
}

/**
* Seals a DashPay private contact request from the sender's encryption key to one of the
* recipient's DashPay keys, signed by `options.signer` with one of the sender's signing keys,
* as its transitions are.
*
* @returns The entry, of the type's entry size, to append.
*/
async sealContactRequest(options: wasm.SealPrivateContactRequestOptions): Promise<Uint8Array> {
const w = await this.sdk.getWasmSdkConnected();
return w.sealPrivateContactRequest(options);
}

/**
* Reads one page of DashPay's private contact request store for the wallet's identities: the
* requests to them, each checked against its sender's signing keys, and the requests they
* sent. It fetches the identities those requests are checked against right after the page, so
* a node serving both can tell which identities the wallet looked up. Keep `nextPosition` as the cursor once the page is stored, and scan
* again from it until `isComplete`.
*/
async scanContactRequests(
options: wasm.ScanPrivateContactRequestsOptions,
): Promise<wasm.PrivateContactRequestScanResult> {
const w = await this.sdk.getWasmSdkConnected();
return w.scanPrivateContactRequests(options);
}
}
4 changes: 4 additions & 0 deletions packages/js-evo-sdk/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ShieldedFacade } from './shielded/facade.js';
import { EncryptedForFacade } from './encrypted-for/facade.js';
import { ModerationChartersFacade } from './moderation-charters/facade.js';
import { DecisionProposalsFacade } from './decision-proposals/facade.js';
import { PrivateDocumentsFacade } from './private-documents/facade.js';

export interface ConnectionOptions {
version?: number;
Expand Down Expand Up @@ -102,6 +103,7 @@ export class EvoSDK {
public encryptedFor!: EncryptedForFacade;
public moderationCharters!: ModerationChartersFacade;
public decisionProposals!: DecisionProposalsFacade;
public privateDocuments!: PrivateDocumentsFacade;
constructor(options: EvoSDKOptions = {}) {
// Apply defaults while preserving any future connection options
const {
Expand Down Expand Up @@ -179,6 +181,7 @@ export class EvoSDK {
this.encryptedFor = new EncryptedForFacade(this);
this.moderationCharters = new ModerationChartersFacade(this);
this.decisionProposals = new DecisionProposalsFacade(this);
this.privateDocuments = new PrivateDocumentsFacade(this);
}

get wasm(): wasm.WasmSdk {
Expand Down Expand Up @@ -457,5 +460,6 @@ export { ShieldedFacade } from './shielded/facade.js';
export { EncryptedForFacade } from './encrypted-for/facade.js';
export { ModerationChartersFacade } from './moderation-charters/facade.js';
export { DecisionProposalsFacade } from './decision-proposals/facade.js';
export { PrivateDocumentsFacade } from './private-documents/facade.js';
export { wallet } from './wallet/functions.js';
export * from './wasm.js';
60 changes: 60 additions & 0 deletions packages/js-evo-sdk/tests/unit/facades/private-documents.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { SinonStub } from 'sinon';
import init, * as wasmSDKPackage from '@dashevo/wasm-sdk';
import { EvoSDK } from '../../../dist/sdk.js';

describe('PrivateDocumentsFacade', () => {
let wasmSdk: wasmSDKPackage.WasmSdk;
let client: EvoSDK;

let getPrivateDocumentEntriesStub: SinonStub;
let sealPrivateContactRequestStub: SinonStub;
let scanPrivateContactRequestsStub: SinonStub;

beforeEach(async function setup() {
await init();
const builder = wasmSDKPackage.WasmSdkBuilder.testnet();
wasmSdk = await builder.build();
client = EvoSDK.fromWasm(wasmSdk);

getPrivateDocumentEntriesStub = this.sinon
.stub(wasmSdk, 'getPrivateDocumentEntries')
.resolves({ entries: [], totalCount: 0n });
sealPrivateContactRequestStub = this.sinon
.stub(wasmSdk, 'sealPrivateContactRequest')
.resolves(new Uint8Array(589));
scanPrivateContactRequestsStub = this.sinon
.stub(wasmSdk, 'scanPrivateContactRequests')
.resolves('scan');
});
Comment thread
QuantumExplorer marked this conversation as resolved.
Resolved

describe('entries()', () => {
it('should forward the contract, type and start position to getPrivateDocumentEntries', async () => {
const big = 9_007_199_254_740_993n; // > Number.MAX_SAFE_INTEGER
const result = await client.privateDocuments.entries('contract', 'privateContactRequest', big);
expect(getPrivateDocumentEntriesStub).to.be.calledOnceWithExactly(
'contract',
'privateContactRequest',
big,
);
expect(result).to.deep.equal({ entries: [], totalCount: 0n });
});
});

describe('sealContactRequest()', () => {
it('should forward the options to sealPrivateContactRequest and return the entry', async () => {
const options = { recipientKeyId: 2 } as unknown as wasmSDKPackage.SealPrivateContactRequestOptions;
const entry = await client.privateDocuments.sealContactRequest(options);
expect(sealPrivateContactRequestStub).to.be.calledOnceWithExactly(options);
expect(entry).to.have.length(589);
});
});

describe('scanContactRequests()', () => {
it('should forward the options to scanPrivateContactRequests', async () => {
const options = { startPosition: 0n };
const result = await client.privateDocuments.scanContactRequests(options);
expect(scanPrivateContactRequestsStub).to.be.calledOnceWithExactly(options);
expect(result).to.equal('scan');
});
});
});
6 changes: 6 additions & 0 deletions packages/rs-context-provider/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ pub trait ContextProvider: Send + Sync {
/// Providers that maintain a writable known-contracts cache (e.g. the
/// mobile `TrustedHttpContextProvider`) override this; the default is a
/// no-op for providers that fetch on demand or don't cache.
///
/// A provider that overrides it keeps a held contract of a higher version
/// than `contract`, deciding atomically under its own cache lock: a
/// registration that waited on a fetch must not replace a newer contract
/// another operation registered meanwhile, which a proof of that newer
/// version would then fail to verify against.
fn register_data_contract(&self, _contract: Arc<DataContract>) {}

/// Fetches the token configuration for a specified token ID.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ pub enum BatchedTransitionBoundsCheck {
},
}

/// Whether a key carrying contract bounds may be used with the documents of one document type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocumentTypeBoundsCheck {
/// The document type lies inside the bounds.
Allowed,
/// The document type lies outside the bounds.
Denied,
/// The bounds name a contract group. The type lies inside them when its contract holds it,
/// or is a whole-contract member, of the group: only state can answer that.
RequiresContractGroupMembership {
/// The group the bounds name.
contract_group_id: Identifier,
},
}

impl ContractBounds {
/// Creates a new contract bounds for the key
pub fn new_from_type(
Expand Down Expand Up @@ -175,6 +190,33 @@ impl ContractBounds {
}
}

/// Whether a key carrying these bounds may be used with the documents of the type
/// `document_type_name` of the contract `contract_id`. Clients scope their ENCRYPTION and
/// DECRYPTION keys with this, as the bounds ask. A contract group bound is answered by the
/// group's members, which only state holds: a client that does not read them treats the
/// type as outside the bounds.
pub fn check_document_type(
&self,
contract_id: &Identifier,
document_type_name: &str,
) -> DocumentTypeBoundsCheck {
match self {
SingleContract { id } if id == contract_id => DocumentTypeBoundsCheck::Allowed,
SingleContractDocumentType {
id,
document_type_name: bound_document_type_name,
} if id == contract_id && bound_document_type_name == document_type_name => {
DocumentTypeBoundsCheck::Allowed
}
SingleContract { .. } | SingleContractDocumentType { .. } => {
DocumentTypeBoundsCheck::Denied
}
ContractGroup { id } => DocumentTypeBoundsCheck::RequiresContractGroupMembership {
contract_group_id: *id,
},
}
}

/// What authorizing `transition` with a key carrying these bounds needs. Consensus uses this
/// to authorize a batch signed by a contract-bound AUTHENTICATION key. Token operations are
/// contract-wide, so a document-type bound never covers them. A contract group bound is
Expand Down Expand Up @@ -376,6 +418,85 @@ mod core_tests {
}
}

#[cfg(test)]
mod document_type_tests {
use super::{ContractBounds, DocumentTypeBoundsCheck};
use crate::identifier::Identifier;

#[test]
fn should_allow_only_the_bound_contract_or_document_type() {
let contract_id = Identifier::from([1; 32]);
let other_id = Identifier::from([2; 32]);
let contract = ContractBounds::SingleContract { id: contract_id };
let document_type = ContractBounds::SingleContractDocumentType {
id: contract_id,
document_type_name: "note".to_string(),
};
for (case, bounds, checked_contract, checked_type, expected) in [
(
"a contract bound, a type of it",
&contract,
contract_id,
"note",
DocumentTypeBoundsCheck::Allowed,
),
(
"a contract bound, another type of it",
&contract,
contract_id,
"other",
DocumentTypeBoundsCheck::Allowed,
),
(
"a contract bound, another contract",
&contract,
other_id,
"note",
DocumentTypeBoundsCheck::Denied,
),
(
"a type bound, that type",
&document_type,
contract_id,
"note",
DocumentTypeBoundsCheck::Allowed,
),
(
"a type bound, another type",
&document_type,
contract_id,
"other",
DocumentTypeBoundsCheck::Denied,
),
(
"a type bound, that type's name in another contract",
&document_type,
other_id,
"note",
DocumentTypeBoundsCheck::Denied,
),
] {
assert_eq!(
bounds.check_document_type(&checked_contract, checked_type),
expected,
"{case}"
);
}
}

#[test]
fn should_leave_a_contract_group_bound_to_its_members() {
let contract_group_id = Identifier::from([3; 32]);
let bounds = ContractBounds::ContractGroup {
id: contract_group_id,
};
assert_eq!(
bounds.check_document_type(&Identifier::from([1; 32]), "note"),
DocumentTypeBoundsCheck::RequiresContractGroupMembership { contract_group_id }
);
}
}

#[cfg(all(test, feature = "json-conversion"))]
mod tests {
use super::*;
Expand Down
43 changes: 43 additions & 0 deletions packages/rs-dpp/src/identity/identity_public_key/security_level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,21 @@ impl SecurityLevel {
pub fn stronger_or_equal_security_than(self: SecurityLevel, rhs: SecurityLevel) -> bool {
(self as u8) <= (rhs as u8)
}

/// The security levels a key may sign a document at when the document's type requires
/// `self` (its `security_level_requirement`): `MASTER` alone when the requirement is
/// `MASTER`, otherwise every level from `CRITICAL` to the requirement, strongest first. A
/// batch takes the levels of the strongest requirement among its documents
/// (`DocumentsBatchTransitionMethodsV0::combined_security_level_requirement`).
pub fn document_signing_levels(self) -> Vec<SecurityLevel> {
if self == Self::MASTER {
return vec![Self::MASTER];
}
Self::full_range()
.into_iter()
.filter(|level| *level != Self::MASTER && (*level as u8) <= (self as u8))
.collect()
}
}

impl std::fmt::Display for SecurityLevel {
Expand All @@ -117,6 +132,34 @@ impl std::fmt::Display for SecurityLevel {
mod tests {
use super::*;

/// Each requirement admits `MASTER` alone, or `CRITICAL` to itself, as a document batch
/// has always computed it.
#[test]
fn should_give_the_levels_each_document_requirement_admits() {
use SecurityLevel::*;
assert_eq!(MASTER.document_signing_levels(), vec![MASTER]);
assert_eq!(CRITICAL.document_signing_levels(), vec![CRITICAL]);
assert_eq!(HIGH.document_signing_levels(), vec![CRITICAL, HIGH]);
assert_eq!(
MEDIUM.document_signing_levels(),
vec![CRITICAL, HIGH, MEDIUM]
);
for requirement in SecurityLevel::full_range() {
let range: Vec<SecurityLevel> = if requirement == MASTER {
vec![MASTER]
} else {
(CRITICAL as u8..=requirement as u8)
.map(|level| SecurityLevel::try_from(level).expect("a security level"))
.collect()
};
assert_eq!(
requirement.document_signing_levels(),
range,
"{requirement}"
);
}
}

// -- TryFrom<u8> valid --
#[test]
fn test_security_level_try_from_u8_all_valid() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::ProtocolError;
use platform_value::Identifier;
#[cfg(feature = "state-transition-signing")]
use platform_version::version::PlatformVersion;
use std::convert::TryFrom;
use crate::state_transition::batch_transition::batched_transition::{BatchedTransition, BatchedTransitionRef};
#[cfg(feature = "state-transition-signing")]
use crate::state_transition::batch_transition::methods::StateTransitionCreationOptions;
Expand Down Expand Up @@ -165,14 +164,12 @@ pub trait DocumentsBatchTransitionMethodsV0: DocumentsBatchTransitionAccessorsV0
}
};

Ok(if highest_security_level == SecurityLevel::MASTER {
vec![SecurityLevel::MASTER]
} else {
// this might seem wrong until you realize that master is 0, critical 1, etc
(SecurityLevel::CRITICAL as u8..=highest_security_level as u8)
.map(|security_level| SecurityLevel::try_from(security_level).unwrap())
.collect()
})
// Edited in place in a shipped generation: `document_signing_levels` returns what the
// code it replaced did for every requirement (MASTER alone for MASTER, otherwise
// CRITICAL through the requirement, strongest first), without its `unwrap`. Its tests
// and the batch tests pin each output, so no protocol version that signs or checks a
// batch through this method can see another result.
Ok(highest_security_level.document_signing_levels())
}

fn set_transitions(&mut self, transitions: Vec<BatchedTransition>);
Expand Down
Loading
Loading